/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * TreePerformanceStats.java
 * Copyright (C) 2007-2012 University of Waikato, Hamilton, New Zealand
 */

package weka.core.neighboursearch;

import java.util.Collections;
import java.util.Enumeration;
import java.util.Vector;

/**
 * The class that measures the performance of a tree based nearest neighbour
 * search algorithm.
 * 
 * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
 * @version $Revision$
 */
public class TreePerformanceStats extends PerformanceStats {

    /** for serialization. */
    private static final long serialVersionUID = -6637636693340810373L;

    // Variables for leaves
    /**
     * The min and max number leaf nodes looked for a query by the tree based NNS
     * algorithm.
     */
    protected int m_MinLeaves, m_MaxLeaves;

    /**
     * The sum of leaf nodes looked at for all the queries.
     */
    protected int m_SumLeaves;
    /**
     * The squared sum of leaf nodes looked at for all the queries.
     */
    protected int m_SumSqLeaves;
    /**
     * The number of leaf nodes looked at for the current/last query.
     */
    protected int m_LeafCount;

    // Variables for internal nodes
    /**
     * The min and max number internal nodes looked for a query by the tree based
     * NNS algorithm.
     */
    protected int m_MinIntNodes, m_MaxIntNodes;
    /**
     * The sum of internal nodes looked at for all the queries.
     */
    protected int m_SumIntNodes;
    /**
     * The squared sum of internal nodes looked at for all the queries.
     */
    protected int m_SumSqIntNodes;
    /**
     * The number of internal nodes looked at for the current/last query.
     */
    protected int m_IntNodeCount;

    /**
     * Default constructor.
     */
    public TreePerformanceStats() {
        reset();
    }

    /**
     * Resets all internal fields/counters.
     */
    public void reset() {
        super.reset();
        // initializing leaf variables
        m_SumLeaves = m_SumSqLeaves = m_LeafCount = 0;
        m_MinLeaves = Integer.MAX_VALUE;
        m_MaxLeaves = Integer.MIN_VALUE;
        // initializing internal variables
        m_SumIntNodes = m_SumSqIntNodes = m_IntNodeCount = 0;
        m_MinIntNodes = Integer.MAX_VALUE;
        m_MaxIntNodes = Integer.MIN_VALUE;
    }

    /**
     * Signals start of the nearest neighbour search. Initializes the stats object.
     */
    public void searchStart() {
        super.searchStart();
        m_LeafCount = 0;
        m_IntNodeCount = 0;
    }

    /**
     * Signals end of the nearest neighbour search. Calculates the statistics for
     * the search.
     */
    public void searchFinish() {
        super.searchFinish();
        // updating stats for leaf nodes
        m_SumLeaves += m_LeafCount;
        m_SumSqLeaves += m_LeafCount * m_LeafCount;
        if (m_LeafCount < m_MinLeaves)
            m_MinLeaves = m_LeafCount;
        if (m_LeafCount > m_MaxLeaves)
            m_MaxLeaves = m_LeafCount;
        // updating stats for internal nodes
        m_SumIntNodes += m_IntNodeCount;
        m_SumSqIntNodes += m_IntNodeCount * m_IntNodeCount;
        if (m_IntNodeCount < m_MinIntNodes)
            m_MinIntNodes = m_IntNodeCount;
        if (m_IntNodeCount > m_MaxIntNodes)
            m_MaxIntNodes = m_IntNodeCount;
    }

    /**
     * Increments the leaf count.
     */
    public void incrLeafCount() {
        m_LeafCount++;
    }

    /**
     * Increments the internal node count.
     */
    public void incrIntNodeCount() {
        m_IntNodeCount++;
    }

    // Getter functions for leaves

    /**
     * Returns the total number of leaves visited.
     * 
     * @return The total number.
     */
    public int getTotalLeavesVisited() {
        return m_SumLeaves;
    }

    /**
     * Returns the mean of number of leaves visited.
     * 
     * @return The mean number of leaves visited.
     */
    public double getMeanLeavesVisited() {
        return m_SumLeaves / (double) m_NumQueries;
    }

    /**
     * Returns the standard deviation of leaves visited.
     * 
     * @return The standard deviation of leaves visited.
     */
    public double getStdDevLeavesVisited() {
        return Math.sqrt((m_SumSqLeaves - (m_SumLeaves * m_SumLeaves) / (double) m_NumQueries) / (m_NumQueries - 1));
    }

    /**
     * Returns the minimum number of leaves visited.
     * 
     * @return The minimum number of leaves visited.
     */
    public int getMinLeavesVisited() {
        return m_MinLeaves;
    }

    /**
     * Returns the maximum number of leaves visited.
     * 
     * @return The maximum number of leaves visited.
     */
    public int getMaxLeavesVisited() {
        return m_MaxLeaves;
    }

    // Getter functions for internal nodes

    /**
     * Returns the total number of internal nodes visited.
     * 
     * @return The total number of internal nodes visited.
     */
    public int getTotalIntNodesVisited() {
        return m_SumIntNodes;
    }

    /**
     * Returns the mean of internal nodes visited.
     * 
     * @return The mean number of internal nodes visited.
     */
    public double getMeanIntNodesVisited() {
        return m_SumIntNodes / (double) m_NumQueries;
    }

    /**
     * Returns the standard deviation of internal nodes visited.
     * 
     * @return The standard deviation of internal nodes visited.
     */
    public double getStdDevIntNodesVisited() {
        return Math.sqrt((m_SumSqIntNodes - (m_SumIntNodes * m_SumIntNodes) / (double) m_NumQueries) / (m_NumQueries - 1));
    }

    /**
     * Returns the minimum of internal nodes visited.
     * 
     * @return The minimum of internal nodes visited.
     */
    public int getMinIntNodesVisited() {
        return m_MinIntNodes;
    }

    /**
     * returns the maximum of internal nodes visited.
     * 
     * @return The maximum of internal nodes visited.
     */
    public int getMaxIntNodesVisited() {
        return m_MaxIntNodes;
    }

    /**
     * Returns an enumeration of the additional measure names.
     * 
     * @return An enumeration of the measure names.
     */
    public Enumeration<String> enumerateMeasures() {
        Vector<String> newVector = new Vector<String>();

        newVector.addAll(Collections.list(super.enumerateMeasures()));

        newVector.addElement("measureTotal_nodes_visited");
        newVector.addElement("measureMean_nodes_visited");
        newVector.addElement("measureStdDev_nodes_visited");
        newVector.addElement("measureMin_nodes_visited");
        newVector.addElement("measureMax_nodes_visited");
        // coord stats
        newVector.addElement("measureTotal_leaves_visited");
        newVector.addElement("measureMean_leaves_visited");
        newVector.addElement("measureStdDev_leaves_visited");
        newVector.addElement("measureMin_leaves_visited");
        newVector.addElement("measureMax_leaves_visited");

        return newVector.elements();
    }

    /**
     * Returns the value of the named measure.
     * 
     * @param additionalMeasureName The name of the measure to query for its value.
     * @return The value of the named measure.
     * @throws IllegalArgumentException If the named measure is not supported.
     */
    public double getMeasure(String additionalMeasureName) throws IllegalArgumentException {
        if (additionalMeasureName.compareToIgnoreCase("measureTotal_nodes_visited") == 0) {
            return (double) getTotalIntNodesVisited();
        } else if (additionalMeasureName.compareToIgnoreCase("measureMean_nodes_visited") == 0) {
            return (double) getMeanIntNodesVisited();
        } else if (additionalMeasureName.compareToIgnoreCase("measureStdDev_nodes_visited") == 0) {
            return (double) getStdDevIntNodesVisited();
        } else if (additionalMeasureName.compareToIgnoreCase("measureMin_nodes_visited") == 0) {
            return (double) getMinIntNodesVisited();
        } else if (additionalMeasureName.compareToIgnoreCase("measureMax_nodes_visited") == 0) {
            return (double) getMaxIntNodesVisited();
        }
        // coord stats
        else if (additionalMeasureName.compareToIgnoreCase("measureTotal_leaves_visited") == 0) {
            return (double) getTotalLeavesVisited();
        } else if (additionalMeasureName.compareToIgnoreCase("measureMean_leaves_visited") == 0) {
            return (double) getMeanLeavesVisited();
        } else if (additionalMeasureName.compareToIgnoreCase("measureStdDev_leaves_visited") == 0) {
            return (double) getStdDevLeavesVisited();
        } else if (additionalMeasureName.compareToIgnoreCase("measureMin_leaves_visited") == 0) {
            return (double) getMinLeavesVisited();
        } else if (additionalMeasureName.compareToIgnoreCase("measureMax_leaves_visited") == 0) {
            return (double) getMaxLeavesVisited();
        } else {
            return super.getMeasure(additionalMeasureName);
        }
    }

    /**
     * Returns a string representation of the statistics.
     * 
     * @return The statistics as string.
     */
    public String getStats() {
        StringBuffer buf = new StringBuffer(super.getStats());

        buf.append("leaves:    " + getMinLeavesVisited() + ", " + getMaxLeavesVisited() + "," + getTotalLeavesVisited() + "," + getMeanLeavesVisited() + ", " + getStdDevLeavesVisited() + "\n");
        buf.append("Int nodes: " + getMinIntNodesVisited() + ", " + getMaxIntNodesVisited() + "," + getTotalIntNodesVisited() + "," + getMeanIntNodesVisited() + ", " + getStdDevIntNodesVisited() + "\n");

        return buf.toString();
    }

}
